/**
 * Classe chargeant les dictionnaires en mémoire et permettant de chercher dedans.
 * 
 * @author Marie-Hélène Goddet, Damien Finck, Cédric Schaller
 * 
 */
package fastwriting;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import utils.Debug;

public class Dico {
	
	private List<List<MotDico>> dico;
	private String fichierDico = "dico/FR.txt";
	
	/**
	 * Constructeur
	 */
	public Dico() {
		Debug.addLog("Création du dictionnaire",Debug.LOG_INFO,"all");
		chargerDicos();
	}

	/**
	 * Constructeur
	 * @param ficDico le dictionnaire principal a charger
	 */
	public Dico(String ficDico) {
		Debug.addLog("Création du dictionnaire",Debug.LOG_INFO,"all");
		changeDico(ficDico);
		chargerDicos();
	}
	/**
	 * Change le dictionnaire principal
	 * et met a jour le dictionnaire
	 * @param dico le fichier dictionnaire a utiliser
	 */
	public void changeDico(String dico){
		fichierDico = dico;
		chargerDicos();
	}
	/**
	 * Charge les dicos en mémoire.
	 * Dico principal et perso.
	 * 
	 */
	private void chargerDicos() {
		int i;
		int idx=-1;
		MotDico mot;
		Scanner sc[] = new Scanner[2];
		String l;
		dico = new ArrayList<List<MotDico>>(26);
		for (i=0;i<26;i++){ //Creation des 26 tableaux
			dico.add(new ArrayList<MotDico>(500));
		}
		
		//Lecture du dictionnaire principal
		InputStream in = getClass().getResourceAsStream(fichierDico);
		if (in == null) {
			Debug.addLog("Problème avec le dictionnaire principal. "+fichierDico+" existe-t-il ?",Debug.LOG_ERREUR,"All");
			return;
		}
		File f = new File(Config.FICHIER_DICO_PERSO);
		
		sc[0] = new Scanner(in);
		try {
			sc[1] = new Scanner(f);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		
		for (int s=0;s<2;s++) {
			while(sc[s].hasNextLine()) {
				l=sc[s].nextLine();
				if (l.length()!=0) { //Eviter les lignes vides 
					mot = new MotDico(l);
					idx = mot.getMotSansAccentMin().charAt(0)-'a';
					if (idx<0 || idx>=26){
						Debug.addLog("Mais qu'avez vous rentrée dans ce dico ? \""+l+"\" est-il vraiment un mot",Debug.LOG_ERREUR,"All");
					} else {
						dico.get(idx).add(mot);
					}
				}
			}
			sc[s].close();
		}
	}
	
	
	/**
	 * Cherche dans le dictionnaire les mots compatibles avec le mot saisi 
	 * @param motSaisi le mot saisi au clavier
	 * @return une liste de mots probables (mot avec une ou zéro erreur)
	 */
	public List<MotProbable> searchDicoPrototype(MotSaisi motSaisi) {
		List<MotProbable> res = new ArrayList<MotProbable>(); //Stocke le resultat de la fonction
		List<MotDico> dicoLettre; //Le dictionnaire de la lettre de debut de motSaisi
		String souris; //Represente le mot saisi 
		String mot2; // Contient le mot sans accent ni majuscule
		boolean majuscule; // Mot rentre en majuscule ? 
		int lev; //le resultat de l algo de Leveinstein
		List<Character> ends;
		
		//Recuperation du dictionnaire
		int index = Character.toUpperCase(motSaisi.getCar(0))-'A';
		if (index >=0 && index<26) {
			dicoLettre = dico.get(index);
		} else {
			Debug.addLog("Les symboles doivent pas arriver ici",Debug.LOG_ERREUR,"All");
			return(res);
		}
		
		//Recuperation du mot a comparer
		souris = motSaisi.toString();
		if (Character.isUpperCase(souris.charAt(0))) //le mot est en majuscule 
		{
			souris = souris.toLowerCase();
			majuscule = true;
		} else {
			majuscule = false;
		}
		
		//Fins de mot possibles
		ends = motSaisi.getPossible_end();
		
		for (MotDico mot : dicoLettre){ //On parcours le dictionnaire
			mot2 = mot.getMotSansAccentMin(); //
			if (ends.contains( mot2.charAt(mot2.length()-1))){ //Lettre de fin possible
				//Algo de Levenstein
				lev = distanceLevenshtein(souris,mot2);
				Debug.addLog("Distance avec "+mot2+" et "+souris+" : "+lev,Debug.LOG_INFO,"lev");
				if (lev!=-2){ //Si le mot n'est pas trop different
					res.add(new MotProbable(mot,majuscule,motSaisi,lev)); //On ajoute le mot
				}
			}
		}
		return res;
	}
	
	/**
	 * Ajoute le mot au dictionnaire perso
	 * @param s le mot a ajouter
	 */
	public void ajouterMot(String s)
	{
		MotDico mot = new MotDico(s);
		//char idx = mot.getMotSansAccentMin().toLowerCase().charAt(0);
		
		int idx = mot.getMotSansAccentMin().charAt(0)-'a';
		dico.get(idx).add(mot);
		
		File f = new File(Config.FICHIER_DICO_PERSO);
		try {
			FileWriter fw = new FileWriter(f,true);
			fw.append("\n"+s);
			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Supprime le mot du dictionnaire perso
	 * @param s le mot a supprimer
	 * @return true si le mot a été trouve, false sinon
	 */
	public boolean supprimerMot(String s)
	{
		File f = new File(Config.FICHIER_DICO_PERSO);
		File src = new File(Config.FICHIER_DICO_PERSO.replace("txt", "lck"));
		Scanner sc;
		File dest;
		try {
			f.renameTo(src);
			
			dest = new File(Config.FICHIER_DICO_PERSO.replace("txt", "tmp"));
			//temp.deleteOnExit();
			FileWriter fw = new FileWriter(dest,false);
			
			sc = new Scanner(src);
			String l;
			while(sc.hasNextLine()) {
				l=sc.nextLine();
				if (!l.equals(s) && l.length()>0)
					fw.append(l+"\n");
			}
			sc.close();
			fw.close();
			
			src.delete();
			dest.renameTo(f);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		MotDico mot = new MotDico(s);
		int idx = mot.getMotSansAccentMin().charAt(0)-'a';
		return(dico.get(idx).remove(mot));
	}
	
	
	/**
	 * Compare le nombre d'erreurs a la distance de Leveinsthein entre les deux mots
	 * @param mot1 le premier mot
	 * @param mot2 le deuxième mot
	 * @return -2 s'il y a trop d'erreurs
	 * @return -1 s'il y a 0 erreur 
	 * @return  l'emplacement du caractère faux s'il y a une erreur
	 */
	private int distanceLevenshtein(String mot1, String mot2){
		int i,j,cout; //Iterateurs et cout 
		int d[][]; //Tableau 
		int couts[][]; //Tableau des couts de passage de remplacement
		int res; //Resultat en renvoyer
		d = new int[mot1.length()+1][mot2.length()+1];
		couts = new int[mot1.length()+1][mot2.length()+1];
		for (i=0;i<=mot1.length();i++)
			d[i][0]=i;
		for (j=0;j<=mot2.length();j++)
			d[0][j]=j;
		for (i=1;i<=mot1.length();i++){
			for (j=1;j<=mot2.length();j++) {
				cout =(mot1.charAt(i-1)==mot2.charAt(j-1))?0:1;
				couts[i][j] = cout;
				//La suppression ne coute rien, le reste 1
				d[i][j]=min(d[i-1][j],d[i][j-1]+1,d[i-1][j-1]+ cout);
			}
		}
		Debug.addLog("Distance avec "+mot2+" : "+d[mot1.length()][mot2.length()],Debug.LOG_INFO,"lev");
		switch (d[mot1.length()][mot2.length()]){
		case 0 : res =-1; break; //Pas d'erreur
		case 1 : res = searchError(d,couts,mot1.length()+1,mot2.length()+1); break; //1 erreur
		default : res = -2; //Plus d'une erreur
		}
		return(res);
	}

	/**
	 * Cherche l'index de l'erreur dans le mot
	 * @param d le tableau de l'algo de Leveinsthein
	 * @param cout le tableau des couts
	 * @param m taille du tableau
	 * @param n taille du tableau
	 * @return le caractère faux ou -1 si problème
	 */
	private int searchError(int[][] d, int[][] cout, int m, int n) {
		int i = m-1; // Place dans le tableau 
		int j = n-1; // Place dans le tableau 
		int res = -1; //Resultat de la fonction
		boolean trouve = false;
		int val; //la valeur au point i,j
		while (!trouve && (i!=0 || j!=0)){
			val = d[i][j];
			if (i>0 && j>0 && val == d[i-1][j-1]+ cout[i][j]){ //remplacement
				if (cout[i][j] == 1){ //Erreur
					res = j-1;
					trouve = true;
				}
				else { //Remplacement par le meme caractere
					i--;
					j--;
				}
			}else if (i>0 && val == d[i-1][j]){ //Suppression
				i--;
			} else if (j>0 && val == d[i][j-1]+1){//Ajout
				res = j-1;
				trouve = true;
			}
		}
		return res;
	}

	/**
	 * Cherche le minimum entre i, j et k
	 * @param i
	 * @param j
	 * @param k
	 * @return le minimum
	 */
	private int min(int i, int j, int k) {
		if (i<j){
			return (i<k)?i:k;
		} else {
			return (j<k)?j:k;
		}
	}
	
	
}
